package com.pxample.pemo.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.ListenableFuture;
import com.pxample.api.CityScore;
import com.pxample.api.CityScore.CityScoreRequest;
import com.pxample.api.CityScore.CityScoreResponse;
import com.pxample.api.cityscoreGrpc;
import com.pxample.config.CustomClientInterceptor;
import com.pxample.pemo.model.ScoreCalculatorRequest;
import com.pxample.pemo.service.ScoreCalculatorService;
import io.grpc.Metadata;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.math.BigInteger;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class ScoreCalculatorServiceImpl implements ScoreCalculatorService {

    //Example: @GrpcClient("myClient") <-> grpc.client.myClient.address=static://localhost:9090
    @GrpcClient("cityscoreclient")
    private cityscoreGrpc.cityscoreStub asyncStub;


    @GrpcClient("cityscoreclient")
    private cityscoreGrpc.cityscoreBlockingStub stub;


    @GrpcClient("cityscoreclient")
    private cityscoreGrpc.cityscoreFutureStub futureStub;

    @Override
    public BigInteger calculateScore (ScoreCalculatorRequest scoreCalculatorRequest) {

        Integer cityScore = getCityScore(scoreCalculatorRequest.getCityCode());
        return BigInteger.valueOf(cityScore.intValue());
    }

    AtomicInteger count = new AtomicInteger();

    @Override
    public CompletableFuture<CityScoreResponse> async(CityScoreRequest cityScoreRequest) {

        CompletableFuture<CityScoreResponse> future = new CompletableFuture<>();

        asyncStub.calculateCityScore(cityScoreRequest, new StreamObserver<CityScore.CityScoreResponse>() {
            @Override
            public void onNext(CityScore.CityScoreResponse cityScoreResponse) {
//                System.err.println(count.incrementAndGet());
                future.complete(cityScoreResponse);
            }

            @Override
            public void onError(Throwable throwable) {
                System.out.println(2222);
            }

            @Override
            public void onCompleted() {

            }
        });
        return future;
    }

    @Override
    public CompletableFuture<JSONObject> asyncMono(CityScoreRequest cityScoreRequest) {
        CompletableFuture<JSONObject> future = new CompletableFuture<>();
        asyncStub.calculateCityScore(cityScoreRequest, new StreamObserver<CityScore.CityScoreResponse>() {
            @Override
            public void onNext(CityScore.CityScoreResponse response) {
                JSONObject params = new JSONObject();
                params.put("status", 0);
                params.put("data", response.getCityScore());
                future.complete(params);
            }

            @Override
            public void onError(Throwable throwable) {
                JSONObject params = new JSONObject();
                params.put("status", 1);
                params.put("data", throwable.getMessage());
                future.complete(params);
            }

            @Override
            public void onCompleted() {

            }
        });
        return future;
    }


    @Override
    public CompletableFuture<JSONObject> asyncNewMono(CityScoreRequest cityScoreRequest) {
        CompletableFuture<JSONObject> future = new CompletableFuture<>();

        asyncStub.newCityScore(cityScoreRequest, new StreamObserver<CityScore.CityScoreResponse>() {
            @Override
            public void onNext(CityScore.CityScoreResponse response) {
                JSONObject params = new JSONObject();
                params.put("status", 0);
                params.put("data", response.getCityScore());
                future.complete(params);
            }

            @Override
            public void onError(Throwable throwable) {
                JSONObject params = new JSONObject();
                params.put("status", 1);
                params.put("data", throwable.getMessage());
                future.complete(params);
            }

            @Override
            public void onCompleted() {

            }
        });
        return future;
    }

    @Override
    public ListenableFuture<CityScoreResponse> future(CityScoreRequest cityScoreRequest) {
        return futureStub.calculateCityScore(cityScoreRequest);
    }

    @Override
    public Mono<CityScoreResponse> asyncSub(CityScoreRequest cityScoreRequest) {
        return null;
    }

    @Override
    public CityScoreResponse sync(CityScoreRequest cityScoreRequest) {
        Metadata metadata = new Metadata();
        metadata.put(Metadata.Key.of("aaa", Metadata.ASCII_STRING_MARSHALLER),"123");
        metadata.put(Metadata.Key.of("Host", Metadata.ASCII_STRING_MARSHALLER),"app-common-gateway.gtjadev.net");
        CityScoreResponse response = stub.withInterceptors(new CustomClientInterceptor(metadata)).withDeadlineAfter(10,TimeUnit.MINUTES).calculateCityScore(cityScoreRequest);
        System.out.println(response.getCityScore());
        return response;
    }

    @Override
    public CityScoreResponse syncNew(CityScoreRequest cityScoreRequest) {
        return stub.newCityScore(cityScoreRequest);
    }


    private Integer getCityScore(Integer cityCode) {
        // build request
        CityScoreRequest cityScoreRequest
                = CityScoreRequest.newBuilder()
                .setCityCode(cityCode)
                .build();

        try {
            System.out.println(cityScoreRequest.getCityCode());
            // sending request and get response
            // Using stub to perform a remote procedure call, e.g. sending request
            // Using stub to execute the function, calculateCityScore(), which is implemented on server
            asyncStub.calculateCityScore(cityScoreRequest, new StreamObserver<CityScoreResponse>() {
                @Override
                public void onNext(CityScoreResponse cityScoreResponse) {
                    System.out.println(cityScoreResponse.getCityScore());
                }

                @Override
                public void onError(Throwable throwable) {
                    System.out.println(2222);
                }

                @Override
                public void onCompleted() {
                    System.out.println(1111);
                }
            });

            // resolve response and return value
        } catch (StatusRuntimeException e) {
            System.out.println("Get reqponse failure ");
            System.out.println("Exception: " + e.getClass().getName());
            System.out.println("Exception Message: " + e.getMessage());
            System.out.println("Exception Cause: " + e.getCause());
            System.out.println("Exception Stack Trace: ");
            e.printStackTrace();
        }
        return Integer.valueOf(1);
    }
}
